/* AlloyFinger v0.1.15
 * By dntzhang
 * Github: https://github.com/AlloyTeam/AlloyFinger
 */
(function () {
  // 获取两点距离
  function getLen(v) {
    return Math.sqrt(v.x * v.x + v.y * v.y);
  }

  function dot(v1, v2) {
    return v1.x * v2.x + v1.y * v2.y;
  }

  // 获取角度
  function getAngle(v1, v2) {
    var mr = getLen(v1) * getLen(v2);
    if (mr === 0) return 0;
    var r = dot(v1, v2) / mr;
    if (r > 1) r = 1;
    return Math.acos(r);
  }

  function cross(v1, v2) {
    return v1.x * v2.y - v2.x * v1.y;
  }

  function getRotateAngle(v1, v2) {
    var angle = getAngle(v1, v2);
    if (cross(v1, v2) > 0) {
      angle *= -1;
    }
    return (angle * 180) / Math.PI;
  }

  var HandlerAdmin = function (el) {
    this.handlers = [];
    this.el = el;
  };

  HandlerAdmin.prototype.add = function (handler) {
    this.handlers.push(handler);
  };

  HandlerAdmin.prototype.del = function (handler) {
    if (!handler) this.handlers = [];

    for (var i = this.handlers.length; i >= 0; i--) {
      if (this.handlers[i] === handler) {
        this.handlers.splice(i, 1);
      }
    }
  };

  HandlerAdmin.prototype.dispatch = function () {
    for (var i = 0, len = this.handlers.length; i < len; i++) {
      var handler = this.handlers[i];
      if (typeof handler === "function") handler.apply(this.el, arguments);
    }
  };

  function wrapFunc(el, handler) {
    var handlerAdmin = new HandlerAdmin(el);
    handlerAdmin.add(handler);

    return handlerAdmin;
  }

  var AlloyFinger = function (el, option) {
    this.element = typeof el == "string" ? document.querySelector(el) : el;

    this.start = this.start.bind(this);
    this.move = this.move.bind(this);
    this.end = this.end.bind(this);
    this.cancel = this.cancel.bind(this);
    this.element.addEventListener("touchstart", this.start, false);
    this.element.addEventListener("touchmove", this.move, false);
    this.element.addEventListener("touchend", this.end, false);
    this.element.addEventListener("touchcancel", this.cancel, false);

    this.preV = {
      x: null,
      y: null,
    };
    this.pinchStartLen = null;
    this.zoom = 1;
    this.isDoubleTap = false;

    var noop = function () {};

    this.rotate = wrapFunc(this.element, option.rotate || noop);
    this.touchStart = wrapFunc(this.element, option.touchStart || noop);
    this.multipointStart = wrapFunc(
      this.element,
      option.multipointStart || noop
    );
    this.multipointEnd = wrapFunc(this.element, option.multipointEnd || noop);
    this.pinch = wrapFunc(this.element, option.pinch || noop);
    this.swipe = wrapFunc(this.element, option.swipe || noop);
    this.tap = wrapFunc(this.element, option.tap || noop);
    this.doubleTap = wrapFunc(this.element, option.doubleTap || noop);
    this.longTap = wrapFunc(this.element, option.longTap || noop);
    this.singleTap = wrapFunc(this.element, option.singleTap || noop);
    this.pressMove = wrapFunc(this.element, option.pressMove || noop);
    this.twoFingerPressMove = wrapFunc(
      this.element,
      option.twoFingerPressMove || noop
    );
    this.touchMove = wrapFunc(this.element, option.touchMove || noop);
    this.touchEnd = wrapFunc(this.element, option.touchEnd || noop);
    this.touchCancel = wrapFunc(this.element, option.touchCancel || noop);

    this._cancelAllHandler = this.cancelAll.bind(this);

    window.addEventListener("scroll", this._cancelAllHandler);

    this.delta = null;
    this.last = null;
    this.now = null;
    this.tapTimeout = null;
    this.singleTapTimeout = null;
    this.longTapTimeout = null;
    this.swipeTimeout = null;
    this.x1 = this.x2 = this.y1 = this.y2 = null;
    this.preTapPosition = {
      x: null,
      y: null,
    };
  };

  AlloyFinger.prototype = {
    start: function (evt) {
      if (!evt.touches) return;
      this.now = Date.now();
      this.x1 = evt.touches[0].pageX;
      this.y1 = evt.touches[0].pageY;
      this.delta = this.now - (this.last || this.now);
      this.touchStart.dispatch(evt, this.element);
      if (this.preTapPosition.x !== null) {
        this.isDoubleTap =
          this.delta > 0 &&
          this.delta <= 250 &&
          Math.abs(this.preTapPosition.x - this.x1) < 30 &&
          Math.abs(this.preTapPosition.y - this.y1) < 30;
        if (this.isDoubleTap) clearTimeout(this.singleTapTimeout);
      }
      this.preTapPosition.x = this.x1;
      this.preTapPosition.y = this.y1;
      this.last = this.now;
      var preV = this.preV,
        len = evt.touches.length;
      if (len > 1) {
        this._cancelLongTap();
        this._cancelSingleTap();
        var v = {
          x: evt.touches[1].pageX - this.x1,
          y: evt.touches[1].pageY - this.y1,
        };
        preV.x = v.x;
        preV.y = v.y;
        this.pinchStartLen = getLen(preV);
        this.multipointStart.dispatch(evt, this.element);
      }
      this._preventTap = false;
      this.longTapTimeout = setTimeout(
        function () {
          this.longTap.dispatch(evt, this.element);
          this._preventTap = true;
        }.bind(this),
        750
      );
    },
    move: function (evt) {
      if (!evt.touches) return;
      var preV = this.preV,
        len = evt.touches.length,
        currentX = evt.touches[0].pageX,
        currentY = evt.touches[0].pageY;
      this.isDoubleTap = false;
      if (len > 1) {
        var sCurrentX = evt.touches[1].pageX,
          sCurrentY = evt.touches[1].pageY;
        var v = {
          x: evt.touches[1].pageX - currentX,
          y: evt.touches[1].pageY - currentY,
        };

        if (preV.x !== null) {
          if (this.pinchStartLen > 0) {
            evt.zoom = getLen(v) / this.pinchStartLen;
            this.pinch.dispatch(evt, this.element);
          }

          evt.angle = getRotateAngle(v, preV);
          this.rotate.dispatch(evt, this.element);
        }
        preV.x = v.x;
        preV.y = v.y;

        if (this.x2 !== null && this.sx2 !== null) {
          evt.deltaX = (currentX - this.x2 + sCurrentX - this.sx2) / 2;
          evt.deltaY = (currentY - this.y2 + sCurrentY - this.sy2) / 2;
        } else {
          evt.deltaX = 0;
          evt.deltaY = 0;
        }
        this.twoFingerPressMove.dispatch(evt, this.element);

        this.sx2 = sCurrentX;
        this.sy2 = sCurrentY;
      } else {
        if (this.x2 !== null) {
          evt.deltaX = currentX - this.x2;
          evt.deltaY = currentY - this.y2;

          //move事件中添加对当前触摸点到初始触摸点的判断，
          //如果曾经大于过某个距离(比如10),就认为是移动到某个地方又移回来，应该不再触发tap事件才对。
          var movedX = Math.abs(this.x1 - this.x2),
            movedY = Math.abs(this.y1 - this.y2);

          if (movedX > 10 || movedY > 10) {
            this._preventTap = true;
          }
        } else {
          evt.deltaX = 0;
          evt.deltaY = 0;
        }

        this.pressMove.dispatch(evt, this.element);
      }

      this.touchMove.dispatch(evt, this.element);

      this._cancelLongTap();
      this.x2 = currentX;
      this.y2 = currentY;

      if (len > 1) {
        evt.preventDefault();
      }
    },
    end: function (evt) {
      if (!evt.changedTouches) return;
      this._cancelLongTap();
      var self = this;
      if (evt.touches.length < 2) {
        this.multipointEnd.dispatch(evt, this.element);
        this.sx2 = this.sy2 = null;
      }

      //swipe
      if (
        (this.x2 && Math.abs(this.x1 - this.x2) > 30) ||
        (this.y2 && Math.abs(this.y1 - this.y2) > 30)
      ) {
        evt.direction = this._swipeDirection(
          this.x1,
          this.x2,
          this.y1,
          this.y2
        );
        this.swipeTimeout = setTimeout(function () {
          self.swipe.dispatch(evt, self.element);
        }, 0);
      } else {
        this.tapTimeout = setTimeout(function () {
          if (!self._preventTap) {
            self.tap.dispatch(evt, self.element);
          }
          // trigger double tap immediately
          if (self.isDoubleTap) {
            self.doubleTap.dispatch(evt, self.element);
            self.isDoubleTap = false;
          }
        }, 0);

        if (!self.isDoubleTap) {
          self.singleTapTimeout = setTimeout(function () {
            self.singleTap.dispatch(evt, self.element);
          }, 250);
        }
      }

      this.touchEnd.dispatch(evt, this.element);

      this.preV.x = 0;
      this.preV.y = 0;
      this.zoom = 1;
      this.pinchStartLen = null;
      this.x1 = this.x2 = this.y1 = this.y2 = null;
    },
    cancelAll: function () {
      this._preventTap = true;
      clearTimeout(this.singleTapTimeout);
      clearTimeout(this.tapTimeout);
      clearTimeout(this.longTapTimeout);
      clearTimeout(this.swipeTimeout);
    },
    cancel: function (evt) {
      this.cancelAll();
      this.touchCancel.dispatch(evt, this.element);
    },
    _cancelLongTap: function () {
      clearTimeout(this.longTapTimeout);
    },
    _cancelSingleTap: function () {
      clearTimeout(this.singleTapTimeout);
    },
    _swipeDirection: function (x1, x2, y1, y2) {
      return Math.abs(x1 - x2) >= Math.abs(y1 - y2)
        ? x1 - x2 > 0
          ? "Left"
          : "Right"
        : y1 - y2 > 0
        ? "Up"
        : "Down";
    },

    on: function (evt, handler) {
      if (this[evt]) {
        this[evt].add(handler);
      }
    },

    off: function (evt, handler) {
      if (this[evt]) {
        this[evt].del(handler);
      }
    },

    destroy: function () {
      if (this.singleTapTimeout) clearTimeout(this.singleTapTimeout);
      if (this.tapTimeout) clearTimeout(this.tapTimeout);
      if (this.longTapTimeout) clearTimeout(this.longTapTimeout);
      if (this.swipeTimeout) clearTimeout(this.swipeTimeout);

      this.element.removeEventListener("touchstart", this.start);
      this.element.removeEventListener("touchmove", this.move);
      this.element.removeEventListener("touchend", this.end);
      this.element.removeEventListener("touchcancel", this.cancel);

      this.rotate.del();
      this.touchStart.del();
      this.multipointStart.del();
      this.multipointEnd.del();
      this.pinch.del();
      this.swipe.del();
      this.tap.del();
      this.doubleTap.del();
      this.longTap.del();
      this.singleTap.del();
      this.pressMove.del();
      this.twoFingerPressMove.del();
      this.touchMove.del();
      this.touchEnd.del();
      this.touchCancel.del();

      this.preV =
        this.pinchStartLen =
        this.zoom =
        this.isDoubleTap =
        this.delta =
        this.last =
        this.now =
        this.tapTimeout =
        this.singleTapTimeout =
        this.longTapTimeout =
        this.swipeTimeout =
        this.x1 =
        this.x2 =
        this.y1 =
        this.y2 =
        this.preTapPosition =
        this.rotate =
        this.touchStart =
        this.multipointStart =
        this.multipointEnd =
        this.pinch =
        this.swipe =
        this.tap =
        this.doubleTap =
        this.longTap =
        this.singleTap =
        this.pressMove =
        this.touchMove =
        this.touchEnd =
        this.touchCancel =
        this.twoFingerPressMove =
          null;

      window.removeEventListener("scroll", this._cancelAllHandler);
      return null;
    },
  };

  if (typeof module !== "undefined" && typeof exports === "object") {
    module.exports = AlloyFinger;
  } else {
    window.AlloyFinger = AlloyFinger;
  }
})();
